using System.Collections.Generic;
using System.Data;
using Fadd.Data.Mappings;

namespace Fadd.Data
{
    /// <summary>
    /// Base interface.
    /// </summary>
    public interface IDataLayer
    {
        /// <summary>
        /// Creates a new transaction.
        /// </summary>
        /// <param name="level">On of the <see cref="IsolationLevel"/>s.</param>
        /// <returns>An object representing the new transaction.</returns>
        /// <remarks>
        /// Once the transaction has completed, you must explicitly commit or roll back the transaction by using the Commit or Rollback methods.
        /// </remarks>
        ITransaction CreateTransaction(IsolationLevel level);

        /// <summary>
        /// Creates a new transaction.
        /// </summary>
        /// <returns>An object representing the new transaction.</returns>
        /// <remarks>
        /// Once the transaction has completed, you must explicitly commit or roll back the transaction by using the Commit or Rollback methods.
        /// </remarks>
        ITransaction CreateTransaction();

        /// <summary>
        /// Gets class that takes care of all providers.
        /// </summary>
        Provider MappingProviders { get; }

        /// <summary>
        /// Get a single object.
        /// </summary>
        /// <typeparam name="T">Type of object to fetch</typeparam>
        /// <param name="statement">complete SQL statement with class/properties instead of table/columns.</param>
        /// <param name="parameters">parameters used in the statement.</param>
        /// <returns>object if found; otherwise null</returns>
        /// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        T Query<T>(string statement, params object[] parameters) where T : new();

        /// <summary>
        /// Executes an statement in the database.
        /// </summary>
        /// <typeparam name="T">Type of object to fetch</typeparam>
        /// <param name="statement">complete SQL statement with class/properties instead of table/columns.</param>
        /// <param name="parameters">parameters used in the statement.</param>
        /// <returns>object if found; otherwise null</returns>
        /// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        void Execute<T>(string statement, params object[] parameters);

        /// <summary>
        /// Get a single object.
        /// </summary>
        /// <typeparam name="T">Type of object to fetch.</typeparam>
        /// <param name="conditions">Conditions (corresponds to statements in the WHERE clause).</param>
        /// <param name="parameters">Parameters used in the where clause.</param>
        /// <returns>object if found; otherwise null.</returns>
        /// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        T Get<T>(string conditions, params object[] parameters) where T : new();

        /// <summary>
        /// Get a single object using its primary key
        /// </summary>
        /// <typeparam name="T">Type of object to fetch, must have a defined mapping.</typeparam>
        /// <param name="id">Primary key value</param>
        /// <returns>object if found; otherwise null.</returns>
        /// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        T GetById<T>(object id) where T : new();



        /// <summary>
        /// Retrieves a collection of items.
        /// </summary>
        /// <typeparam name="T">Type of item to get.</typeparam>
        /// <param name="conditions">Conditions to limit.</param>
        /// <param name="parameters">Parameters used in the condition.</param>
        /// <returns>A list of objects; or an empty list</returns>
        IList<T> Find<T>(string conditions, params object[] parameters) where T : new();

        /// <summary>
        /// Retrieves all items of a certain type.
        /// </summary>
        /// <typeparam name="T">Type of item to get.</typeparam>
        /// <returns>A list of objects; or an empty list</returns>
        IList<T> Find<T>() where T : new();

        /// <summary>
        /// Gets all items defined by the <see cref="Statement"/>.
        /// </summary>
        /// <typeparam name="T">type of items to get.</typeparam>
        /// <param name="statement">Statement specifying which elements to get.</param>
        /// <returns>A list of elements.</returns>
        IList<T> Find<T>(Statement statement) where T : new();

		/// <summary>
		/// Execute a custom SQL query (Object SQL can not be used yet).
		/// </summary>
		/// <typeparam name="T">type of items to get.</typeparam>
		/// <param name="query">SQL query.</param>
		/// <param name="parameters">Parameters used in the query.</param>
		/// <returns>A list of elements.</returns>
		IList<T> FindBySQL<T>(string query, params object[] parameters) where T : new();


        /// <summary>
        /// Save or update an object in the database (1. Checks if the PK is empty. 2. Checks if the object exists in the db).
        /// </summary>
        /// <param name="value">Value to create/update.</param>
        /// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        void Save(object value);

        /// <summary>
        /// Create a new object in the database.
        /// </summary>
        /// <param name="value">Object to create.</param>
        /// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        void Create(object value);

        /// <summary>
        /// Update an existing object.
        /// </summary>
        /// <param name="value">Object to update</param>
        /// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        void Update(object value);

        /// <summary>
        /// Checks if an object exists by using it's primary key.
        /// </summary>
        /// <typeparam name="T">Type of object to check</typeparam>
        /// <param name="id">Primary key</param>
        /// <returns>true if object was found; otherwise false.</returns>
        /// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        bool ExistsById<T>(object id);

        /// <summary>
        /// Checks if an object exists by providing it.
        /// </summary>
        /// <param name="model">object to check</param>
        /// <returns>true if object is stored in the database; otherwise false.</returns>
        /// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        bool Exists(object model);

        /// <summary>
        /// Checks if an object exists in the database
        /// </summary>
        /// <typeparam name="T">Type of object to check.</typeparam>
        /// <param name="conditions">Conditions.</param>
        /// <param name="arguments">Arguments used in the conditions.</param>
        /// <returns>true if object is stored in the database; otherwise false.</returns>
        /// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        bool Exists<T>(string conditions, params object[] arguments);

		/// <summary>
		/// Count number of items.
		/// </summary>
		/// <typeparam name="T">Type of object to check.</typeparam>
		/// <param name="conditions">Conditions.</param>
		/// <param name="arguments">Arguments used in the conditions.</param>
		/// <returns>true if object is stored in the database; otherwise false.</returns>
		/// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
		/// <exception cref="DataLayerException">If data layer fails.</exception>
		int Count<T>(string conditions, params object[] arguments);

		/// <summary>
		/// Count number of items.
		/// </summary>
		/// <typeparam name="T">Type of object to check.</typeparam>
		/// <returns>true if object is stored in the database; otherwise false.</returns>
		/// <exception cref="MappingException">If the class/table mapping is incorrect.</exception>
		/// <exception cref="DataLayerException">If data layer fails.</exception>
		int Count<T>();

        /// <summary>
        /// Counts by SQL. Write the whole statement yourself.
        /// </summary>
        /// <param name="sql">The statement.</param>
        /// <returns>Number of returned rows</returns>
        /// <exception cref="DataLayerException">If data layer fails.</exception>
        int CountBySQL(string sql);

        /// <summary>
        /// Removes an object by using it's id property.
        /// </summary>
        /// <typeparam name="T">Type of object to remove.</typeparam>
        /// <param name="id">Primary key value</param>
        /// <exception cref="MappingException">RemoveById requires exactly one primary key.</exception>
        void RemoveById<T>(object id);

        /// <summary>
        /// Removes an object by using it's id property.
        /// </summary>
        /// <typeparam name="T">Type of object to remove.</typeparam>
        void Remove<T>(string conditions, params object[] parameters);

		/// <summary>
		/// Gets logger instance.
		/// </summary>
		IGenericLogger Logger { get; }

    }
}